/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package gride.plugin.editor;

import gride.events.EventListener;
import gride.events.GrideEvents;
import gride.exceptions.UserCancelException;
import gride.plugin.GridePlugin;
import gride.gui.GUIManager;
import gride.project.GrideScript;
import gride.groovy.Regex;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.Document;
import javax.swing.undo.UndoManager;
import jsyntaxpane.DefaultSyntaxKit;

/**
 *
 * @author lukasz
 */
public class EditorPlugin extends GridePlugin
{
    // <editor-fold defaultstate="expanded" desc="Private Members">

    private List<EditorTab> editorTabs;
    
    private JTabbedPane tabbedPane;

    JMenuItem saveMenuItem;
    JMenuItem closeMenuItem;
    JMenuItem closeAllMenuItem;

    // </editor-fold>


    // <editor-fold defaultstate="expanded" desc="Constructors">

    /**
     * 
     */
    public EditorPlugin()
    {
        super();

        editorTabs = new ArrayList<EditorTab>();
        tabbedPane = new JTabbedPane();

        // create the Save menu entry
        saveMenuItem = new JMenuItem("Save");
        saveMenuItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
                                    java.awt.event.KeyEvent.VK_S,
                                    java.awt.event.InputEvent.CTRL_MASK));
        saveMenuItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                EditorTab tab = (EditorTab) tabbedPane.getSelectedComponent();
                tab.saveFile();

                checkIfTabModified(tab);
            }
        });
        saveMenuItem.setEnabled(false);

        // create the Close File menu entry
        closeMenuItem = new JMenuItem("Close File");
        closeMenuItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
                                     java.awt.event.KeyEvent.VK_W,
                                     java.awt.event.InputEvent.CTRL_MASK));
        closeMenuItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                try
                {
                    EditorTab tab =
                            (EditorTab) tabbedPane.getSelectedComponent();
                    closeFile(tab.getFile());
                }
                catch (UserCancelException ex)
                {
                    // user cancelled
                }
            }
        });
        closeMenuItem.setEnabled(false);

        // create the Close All menu entry
        closeAllMenuItem = new JMenuItem("Close All");
        closeAllMenuItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                try
                {
                    closeAll();
                }
                catch (UserCancelException ex)
                {
                    return;
                }
            }
        });
        closeAllMenuItem.setEnabled(false);

        // on each tab selection change, make sure apropriate menu entries
        // are enabled
        tabbedPane.addChangeListener(new ChangeListener()
        {
            public void stateChanged(ChangeEvent e)
            {
                saveMenuItem.setEnabled(true);
                closeMenuItem.setEnabled(true);
                closeAllMenuItem.setEnabled(true);

                EditorTab tab = (EditorTab) tabbedPane.getSelectedComponent();

                if (tab == null)
                {
                    saveMenuItem.setEnabled(false);
                    closeMenuItem.setEnabled(false);

                    projectManager.setActiveFile(null);
                }
                else
                {
                    if (!tab.isModified())
                        saveMenuItem.setEnabled(false);

                    // set the active file
                    projectManager.setActiveFile(tab.getFile());

                    // request focus on the editor
                    tab.requestFocus();
                }

                if (tabbedPane.getComponentCount() == 0)
                    closeAllMenuItem.setEnabled(false);
            }
        });

        DefaultSyntaxKit.initKit();
    }

    // </editor-fold>


    // <editor-fold defaultstate="expanded" desc="Getters/Setters">

    // </editor-fold>


    // <editor-fold defaultstate="expanded" desc="Public Methods">

    /**
     *
     */
    @Override
    public void activate()
    {
        guiManager.addComponent(GUIManager.MAIN_PANE, tabbedPane,
                                "Source Editor");
        guiManager.focusOnComponent(tabbedPane);

        // add menu entries
        guiManager.addMenuItem(saveMenuItem, "File");
        guiManager.addMenuItem(closeMenuItem, "File");
        guiManager.addMenuItem(closeAllMenuItem, "File");

        // register listeners
        final Object myself = this;

        // add a listener waiting for OPEN_FILE events
        eventManager.addListener(GrideEvents.OPEN_FILE, new EventListener()
        {
            public Object whoIsListening()
            {
                return myself;
            }

            public void eventRaised(String eventID, Object[] args)
            {
                openFile((GrideScript) args[0]);
            }
        });

        // add a listener waiting for CLOSE_FILE events
        eventManager.addListener(GrideEvents.CLOSE_FILE, new EventListener()
        {
            public Object whoIsListening()
            {
                return myself;
            }

            public void eventRaised(String eventID, Object[] args)
            {
                closeFile((GrideScript) args[0]);
            }
        });


        // this listener will be registered for CLOSING_PROJECT
        // and EXITING_GRIDE events
        EventListener closeListener = new EventListener()
        {
            public Object whoIsListening()
            {
                return myself;
            }

            public void eventRaised(String eventID, Object[] args)
            {
                closeAll();
            }
        };

        // add the above listener to the event manager
        eventManager.addListener(GrideEvents.CLOSING_PROJECT, closeListener);
        eventManager.addListener(GrideEvents.EXITING_GRIDE, closeListener);

        // add a listener waiting for EXECUTING_FILE events
        eventManager.addListener(GrideEvents.EXECUTING_FILE, new EventListener()
        {
            public Object whoIsListening()
            {
                return myself;
            }

            public void eventRaised(String eventID, Object[] args)
            {
                // if this file is open for editing,
                // make sure it's saved before execution
                for (EditorTab tab : editorTabs)
                    if (tab.getFile() == (GrideScript)args[0] &&
                                                               tab.isModified())
                    {
                        tab.saveFile();
                        checkIfTabModified(tab);
                    }
            }
        });

        // add a listener waiting for SAVE_ALL_FILES events
        eventManager.addListener(GrideEvents.SAVE_ALL_FILES, new EventListener()
        {
            public Object whoIsListening()
            {
                return myself;
            }

            public void eventRaised(String eventID, Object[] args)
            {
                // if this file is open for editing,
                // make sure it's saved before execution
                for (EditorTab tab : editorTabs)
                    if (tab.isModified())
                    {
                        tab.saveFile();
                        checkIfTabModified(tab);
                    }
            }
        });

        // add a listener waiting for HIGHLIGHT_FILE_LINE events
        eventManager.addListener(GrideEvents.HIGHLIGHT_FILE_LINE,
            new EventListener()
        {
            public Object whoIsListening()
            {
                return myself;
            }

            public void eventRaised(String eventID, Object[] args)
            {
                openFile((GrideScript) args[0]);

                EditorTab tab = (EditorTab) tabbedPane.getSelectedComponent();
                tab.highlightLine((Integer) args[1], (Color) args[2]);
            }
        });

        // add a listener waiting for PROJECT_FILE_RENAMED events
        eventManager.addListener(GrideEvents.PROJECT_FILE_RENAMED,
                                 new EventListener()
        {
            public Object whoIsListening()
            {
                return myself;
            }

            public void eventRaised(String eventID, Object[] args)
            {
                try
                {
                    boolean wasOpened = false;

                    GrideScript file = (GrideScript) args[0];

                    // if this file is being edited, save it
                    for (EditorTab t : editorTabs)
                        if (t.getFile() == file)
                        {
                            wasOpened = true;

                            t.saveFile();
                            closeFile(file);
                            break;
                        }

                    EditorTab tab = new EditorTab(file);

                    String contents = tab.getDocument().getText(0,
                                                 tab.getDocument().getLength());

                    String prevPackage =
                                       Regex.getPackageNameFromSource(contents);

                    if (!prevPackage.equals("") &&
                        !file.getPackage().equals(""))
                       contents = contents.replace("package " + prevPackage,
                                                "package " + file.getPackage());

                    else if (!prevPackage.equals("") &&
                              file.getPackage().equals(""))
                        contents = contents.replace("package " + prevPackage,
                                                    "");

                    else if (prevPackage.equals("") &&
                             !file.getPackage().equals(""))
                        contents = "package " + file.getPackage() + "\n"
                                                                     + contents;
                    
                    tab.setText(contents);
                    tab.saveFile();

                    if (wasOpened)
                        openFile(file);
                }
                catch (Exception ex)
                {
                    return;
                }
            }
        });

        active = true;
    }

    /**
     *
     */
    @Override
    public void deactivate()
    {
        guiManager.removeComponent(tabbedPane);
        
        guiManager.removeMenuItem(saveMenuItem);
        guiManager.removeMenuItem(closeMenuItem);
        guiManager.removeMenuItem(closeAllMenuItem);

        eventManager.removeAllListenersFrom(this);

        active = false;
    }

    // </editor-fold>


    // <editor-fold defaultstate="expanded" desc="Private Methods">

    /**
     * Opens the given file in an editor tab
     * @param file
     */
    private void openFile(GrideScript file)
    {
        // first check if the file isn't opened already
        for (EditorTab tab : editorTabs)
            if (tab.getFile() == file)
            {
                // file already open - just select the tab
                tabbedPane.setSelectedComponent(tab);
                tabbedPane.requestFocus();
                return;
            }

        // otherwise we have to open the file in a new tab
        try
        {
            final EditorTab tab = new EditorTab(file);
            final UndoManager undoManager = new UndoManager();

            tab.getDocument().addUndoableEditListener(new UndoableEditListener()
            {
                public void undoableEditHappened(UndoableEditEvent e)
                {
                    undoManager.addEdit(e.getEdit());
                    checkPackageDeclaration(tab, undoManager);
                    checkIfTabModified(tab);
                }
            });

            editorTabs.add(tab);
            tabbedPane.addTab(file.getSourcefile().getName(), tab);

            // select the new tab
            tabbedPane.setSelectedComponent(tab);
            tabbedPane.requestFocus();
        }
        catch (IOException ex)
        {
            // an error occured while reading file contents
            JOptionPane.showMessageDialog(null,
                        "Couldn't read file " + file.getSourcefile().getName(),
                        "Error opening file", JOptionPane.ERROR_MESSAGE);
        }
    }


    /**
     * Closes the given file, if open
     * @param file
     */
    private void closeFile(GrideScript file)
    {
        // find the tab containing the file
        for (EditorTab tab : editorTabs)
            if (tab.getFile() == file)
            {
                // raise even notifying of closing the file
                Object[] args = { file };
                eventManager.raiseEvent(GrideEvents.CLOSING_FILE, args);

                // check if the file has been modified
                if (tab.isModified())
                {
                    // give the user a chance to save the file
                    int answer = JOptionPane.showConfirmDialog(null,
                                            "Save file " + file.getName() + "?",
                                            "File modified",
                                            JOptionPane.YES_NO_CANCEL_OPTION,
                                            JOptionPane.QUESTION_MESSAGE);

                    if (answer == JOptionPane.CANCEL_OPTION)
                        throw new UserCancelException();

                    if (answer == JOptionPane.YES_OPTION)
                        tab.saveFile();
                }

                // close the tab
                tabbedPane.remove(tab);
                editorTabs.remove(tab);

                return;
            }
    }


    /**
     * Closes all opened files
     */
    private void closeAll()
    {
        // close one by one
        while (!editorTabs.isEmpty())
            closeFile(editorTabs.get(0).getFile());
    }


    /**
     * Makes sure that the tab header indicates if the file has been modified
     * and that the Save menu entry is enabled
     * @param tab
     */
    private void checkIfTabModified(EditorTab tab)
    {
        int index = tabbedPane.indexOfComponent(tab);

        tab.getFile().setModified(tab.isModified());

        if (tab.getFile().isModified())
        {
            tabbedPane.setTitleAt(index, "(*) " +
                                  tab.getFile().getSourcefile().getName());
            saveMenuItem.setEnabled(true);
        }
        else
        {
            tabbedPane.setTitleAt(index,
                                  tab.getFile().getSourcefile().getName());
            saveMenuItem.setEnabled(false);
        }
    }


    /**
     * Makes sure that the package declaration in the text is the same as
     * the package set in the edited GrideScript. This should be called
     * whenever a change is made in the editor, as upon discovering wrong
     * package declaration it will force an undo in the editor by simulating
     * a Ctrl+Z key event
     *
     * @param tab
     */
    private void checkPackageDeclaration(EditorTab tab, UndoManager undoManager)
    {
        // this flag will be set in files that don't need package control
        if (!tab.getFile().isControlledPackage())
            return;

        try
        {
            // get the current text
            String text;
            Document doc = tab.getDocument();
            text = doc.getText(0, doc.getLength());

            // get the package as in current text
            String pack = Regex.getPackageNameFromSource(text);

            // check if it is what it should be
            if (!pack.equals(tab.getFile().getPackage()))
            {
                // display message warning that package declaration incorrect
                JOptionPane.showMessageDialog(null, "In order to move this " +
                        "file to a different package, right-click on it in" +
                        "the project browser.", "Invalid package declaration",
                        JOptionPane.WARNING_MESSAGE);

                // undo the operation
                undoManager.undo();
            }
        }
        catch (Exception e)
        {
            return;
        }
    }
    // </editor-fold>

}
